﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

using Newtonsoft.Json.Linq;

using Utils.Http;
using Utils.Weixin.Models;
using Utils.Weixin.Models.Open;


namespace Utils.Weixin
{
    public class WeChatOpen : WeChatService
    {
        private readonly ILogger<WeChatOpen> logger;
        private readonly IServiceProvider serviceProvider;

        public WeChatOpen(SecretOptions options, IServiceProvider serviceProvider, ICacheStrategy cacheStrategy, ILogger<WeChatOpen> logger) : base(options, cacheStrategy)
        {
            this.logger = logger;
            this.serviceProvider = serviceProvider;
        }


        public async Task<AuthorizationInfo> Authorization(string authorization_code)
        {
            string url = await GetUrl("cgi-bin/component/api_query_auth");
            var token = await SendAsync(HttpMethod.Post, url, new
            {
                authorization_code,
                component_appid = options.AppId
            });
            var authorizationInfo = token["authorization_info"].ToObject<AuthorizationInfo>();
            cacheStrategy.Add("token:" + authorizationInfo.authorizer_appid, authorizationInfo.authorizer_access_token, authorizationInfo.expires_in);
            return authorizationInfo;
        }

        public async Task<AuthorizerInfo> GetAuthorizerInfo(string authorizer_appid)
        {
            string url = await GetUrl("cgi-bin/component/api_get_authorizer_info");
            var token = await SendAsync(HttpMethod.Post, url, new
            {
                authorizer_appid,
                component_appid = options.AppId
            }, false);
            var authorizerInfo = token["authorizer_info"].ToObject<AuthorizerInfo>();
            return authorizerInfo;
        }

        public async Task<List<TemplateInfo>> GetTempList(int? templateType)
        {
            string url = await GetUrl("wxa/gettemplatelist");
            if (templateType.HasValue)
            {
                url = url.AppendQuery(new { template_type = templateType.Value });
            }
            var token = await SendAsync(HttpMethod.Get, url, null, false);
            var list = token["template_list"].ToObject<List<TemplateInfo>>();
            return list;
        }

        /// <summary>
        /// 开放平台扩展小程序
        /// </summary>
        /// <param name="authorizer_appid"></param>
        /// <param name="authorizer_refresh_token"></param>
        /// <returns></returns>
        public WeChatMiniForOpen CreateWeChatMiniForOpen(string authorizer_appid, string authorizer_refresh_token)
        {
            return ActivatorUtilities.CreateInstance<WeChatMiniForOpen>(serviceProvider, this, new SecretOptions { AppId = authorizer_appid, AppSecret = authorizer_refresh_token });
        }

        /// <summary>
        /// 普通小程序
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="appSecret"></param>
        /// <returns></returns>
        public WeChatMini CreateWeChatMini(string appId, string appSecret)
        {
            return ActivatorUtilities.CreateInstance<WeChatMini>(serviceProvider, new SecretOptions { AppId = appId, AppSecret = appSecret });
        }

        public WeChatOfficial CreateWeChatOfficial(string appId, string appSecret)
        {
            return ActivatorUtilities.CreateInstance<WeChatOfficial>(serviceProvider, new SecretOptions { AppId = appId, AppSecret = appSecret });
        }


        public async Task<string> GetPreAuthcCode()
        {
            string url = await GetUrl("cgi-bin/component/api_create_preauthcode");
            var token = await SendAsync(HttpMethod.Post, url, new
            {
                component_appid = options.AppId
            });
            return token.Value<string>("pre_auth_code");
        }

        /// <summary>
        /// 获取PC授权页面
        /// </summary>
        /// <param name="preAuthcode"></param>
        /// <param name="auth_type"><![CDATA[ 1 表示手机端仅展示公众号；
        /// 2 表示仅展示小程序；
        /// 3 表示公众号和小程序都展示；
        ///  4表示小程序推客账号；
        ///  5表示视频号账号；
        ///  6表示全部，即公众号、小程序、视频号都展示]]></param>
        /// <returns></returns>
        public string BuilderPcUrl(string preAuthcode, string redirect_uri, int auth_type = 2)
        {
            redirect_uri = redirect_uri.UrlEncode();
            return "https://mp.weixin.qq.com/cgi-bin/componentloginpage" +
                $"?component_appid={options.AppId}&pre_auth_code={preAuthcode}&redirect_uri={redirect_uri}&auth_type={auth_type}";
        }

        /// <summary>
        /// 获取H5授权页面
        /// </summary>
        /// <param name="auth_type"><![CDATA[ 1 表示手机端仅展示公众号；
        /// 2 表示仅展示小程序；
        /// 3 表示公众号和小程序都展示；
        ///  4表示小程序推客账号；
        ///  5表示视频号账号；
        ///  6表示全部，即公众号、小程序、视频号都展示]]></param>
        /// <returns></returns>
        public string BuilderH5Url(string preAuthcode, string redirect_uri, int auth_type = 2)
        {
            redirect_uri = redirect_uri.UrlEncode();
            return "https://open.weixin.qq.com/wxaopen/safe/bindcomponent" +
                $"?action=bindcomponent&no_scan=1&component_appid={options.AppId}&pre_auth_code={preAuthcode}&redirect_uri={redirect_uri}&auth_type={auth_type}#wechat_redirect";
        }

        private async Task<string> GetUrl(string path)
        {
            var token = await GetAccessTokenForCache();
            return path + $"?access_token={token}&component_access_token={token}";
        }

        private async Task<string> AuthorizerTokenForCache(string authorizer_appid, string authorizer_refresh_token)
        {
            var token = cacheStrategy.Get<string>("token:" + authorizer_appid);
            if (token.IsNullOrEmpty())
            {
                string url = await GetUrl("cgi-bin/component/api_authorizer_token");
                var jtoken = await SendAsync(HttpMethod.Post, url, new
                {
                    authorizer_appid,
                    component_appid = options.AppId,
                    authorizer_refresh_token
                });
                token = jtoken["authorizer_access_token"].ToString();
                var expiresIn = jtoken.Value<int>("expires_in");
                cacheStrategy.Add("token:" + authorizer_appid, token, expiresIn - 100);
            }
            return token;
        }

        public override async Task<(string access_token, int expires_in)> GetAccessToken()
        {
            string key = "ticket:" + options.AppId;
            string ticket = cacheStrategy.Get<string>(key);
            if (ticket.IsNullOrWhiteSpace())
            {
                throw Error.SystemError("无启动票据");
            }
            string url = baseUrl + "cgi-bin/component/api_component_token";
            var token = await httpClient.PostAsync(url, new JsonContent(new
            {
                component_appid = options.AppId,
                component_appsecret = options.AppSecret,
                component_verify_ticket = ticket,
            })).ReadAsWxOpenAsync();
            return (token.Value<string>("component_access_token"), token.Value<int>("expires_in"));
        }


        public void SetTicket(string appId, string componentVerifyTicket)
        {
            cacheStrategy.Add("ticket:" + appId, componentVerifyTicket);
        }

        ~WeChatOpen()
        {
            httpClient.Dispose();
        }

        public class WeChatMiniForOpen : WeChatMini
        {
            private readonly WeChatOpen weChatOpen;

            private readonly string authorizer_refresh_token;

            private readonly string appid;
            public WeChatMiniForOpen(WeChatOpen weChatOpen, SecretOptions secretOptions, ICacheStrategy cacheStrategy) : base(secretOptions, cacheStrategy)
            {
                this.weChatOpen = weChatOpen;
                this.appid = secretOptions.AppId;
                this.authorizer_refresh_token = secretOptions.AppSecret;
            }


            public override Task<string> GetAccessTokenForCache()
            {
                return weChatOpen.AuthorizerTokenForCache(appid, authorizer_refresh_token);
            }

            public override async Task<Jscode2Session> Jscode2session(string js_code)
            {
                var jtoken = await SendAsync(HttpMethod.Get, "sns/component/jscode2session", new
                {
                    component_access_token = await weChatOpen.GetAccessTokenForCache(), //这里特殊
                    appid,
                    js_code,
                    component_appid = weChatOpen.options.AppId,
                    grant_type = "authorization_code"
                }, false);
                return jtoken.ToObject<Jscode2Session>();
            }

            public async Task Commot(int template_id, string ext_json, string user_version, string user_desc)
            {
                await SendAsync(HttpMethod.Post, "wxa/commit", new
                {
                    template_id,
                    ext_json,
                    user_version,
                    user_desc
                });
            }

            /// <summary>
            /// 获取体验码
            /// </summary>
            /// <returns></returns>
            public async Task<Stream> GetSampleQrcode()
            {
                return await SendStreamAsync(HttpMethod.Get, "wxa/get_qrcode", new { });
            }

            /// <summary>
            /// 提交审核
            /// </summary>
            public async Task<long> SubmitAudit()
            {
                var privacy_ver = 2;
                var privacyKeys = await GetPrivacySetting(privacy_ver);
                if (!privacyKeys.IsEmpty())
                {
                    await SetPrivacySetting(privacyKeys, "13735833064", privacy_ver);
                }
                var jtoken = await SendAsync(HttpMethod.Post, "wxa/submit_audit", new
                {
                    item_list = new string[0],
                    order_path = "pagesOrder/order/index",
                });
                return jtoken.Value<long>("auditid");
            }

            public async Task UndocodeAudit()
            {
                var jtoken = await SendAsync(HttpMethod.Get, "wxa/undocodeaudit", new
                {
                });
            }
            public async Task GetCodePrivacyInfo()
            {
                var jtoken = await SendAsync(HttpMethod.Get, "wxa/security/get_code_privacy_info", new { });
                Console.WriteLine(jtoken);
            }

            public async Task<string[]> GetPrivacySetting(int privacy_ver = 2)
            {
                var jtoken = await SendAsync(HttpMethod.Post, "cgi-bin/component/getprivacysetting", new { privacy_ver });
                var code_exist = jtoken["code_exist"].ToObject<int>();
                if (code_exist == 0)
                {
                    throw Error.DefaultError("请先构建代码");
                }
                var privacyList = jtoken["privacy_list"].ToObject<string[]>();
                var settingList = (JArray)jtoken["setting_list"];
                foreach (JToken item in settingList)
                {
                    var privacyKey = item["privacy_key"].ToString();
                    var privacyText = item["privacy_text"].ToString();
                    if (string.IsNullOrEmpty(privacyText))
                    {
                        return privacyList;
                    }
                }
                return new string[0];
            }

            public async Task SetPrivacySetting(string[] settings, string contact_phone, int privacy_ver = 2)
            {
                var enums = EnumUtil.GetEnums<DefaultPrivacy>();
                var setting_list = settings.Select(privacy_key => new
                {
                    privacy_key,
                    privacy_text = enums.Find(a => a.Name == privacy_key)?.Description
                }).ToArray();
                var jtoken = await SendAsync(HttpMethod.Post, "cgi-bin/component/setprivacysetting", new
                {
                    privacy_ver,
                    setting_list,
                    owner_setting = new
                    {
                        contact_phone,
                        notice_method = "手机"
                    },
                    order_path = "pagesOrder/order/index",
                });
            }


            public async Task Unbind()
            {
                await SendAsync(HttpMethod.Post, "cgi-bin/open/unbind", new
                {
                    open_appid = appid
                });
            }


            /// <summary>
            /// 查询审核状态
            /// </summary>
            /// <param name="auditid"></param>
            /// <returns></returns>
            public async Task<(bool success, string reason)> GetAuditStatus(long auditid)
            {
                var jtoken = await SendAsync(HttpMethod.Post, "wxa/get_auditstatus", new
                {
                    auditid
                });
                return (jtoken.Value<int>("status") == 0, jtoken["reason"]?.ToString());
            }

            public async Task<JToken> ServerDomain(ActionType action, string requestDomain, string imgDomain)
            {
                return await ServerDomain(action, new string[] { requestDomain }, string.IsNullOrEmpty(imgDomain) ? new string[0] : new string[] { imgDomain });
            }

            public async Task<JToken> ServerDomain(ActionType action, string[] requestDomain, string[] imgDomain)
            {
                JToken token = await SendAsync(HttpMethod.Post, "wxa/modify_domain", new
                {
                    action = action.ToString(),
                    requestdomain = requestDomain,
                    wsrequestdomain = requestDomain.Select(a => a.Replace("http", "ws")).ToArray(),
                    uploaddomain = requestDomain,
                    downloaddomain = requestDomain.Concat(imgDomain).ToArray(),
                });
                return token;
            }

            public async Task<JToken> WebviewDomain(ActionType action, params string[] domains)
            {
                JToken token = await SendAsync(HttpMethod.Post, "wxa/setwebviewdomain", new
                {
                    action = action.ToString(),
                    webviewdomain = domains,
                });
                return token;
            }
            /// <summary>
            /// 发布
            /// </summary>
            /// <returns></returns>
            public async Task Release()
            {
                await SendAsync(HttpMethod.Post, "wxa/release", new { });
            }


        }
    }

    public enum DefaultPrivacy
    {
        [Description("给客户提供更好体验服务")]
        UserInfo,
        [Description("更便捷的定位到附件的门店")]
        Location,
        [Description("方便可以填写发货信息")]
        Address,
        [Description("快捷填写发票信息")]
        Invoice,
        [Description("方便客户获取运动数据")]
        RunData,
        [Description("简化售后沟通方式")]
        Record,
        [Description("提供商品图片提供更好售后服务")]
        Album,
        [Description("保存商品图片到本地")]
        AlbumWriteOnly,
        [Description("提供商品视频提供更好售后服务")]
        Camera,
        [Description("给客户提供更好售后服务")]
        PhoneNumber,
        [Description("更便捷的定位到附件的门店")]
        ChooseLocation,
        Clipboard

    }
    public enum ActionType
    {
        add,
        set,
        delete,
        get
    }
}
